﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;

namespace Organized
{
    // A 3D heightmap terrain generated from a heightmap texture
    public class TerrainShift : Component, I3DComponent
    {


        Effect effect;

        //VertexPositionNormalColored[] vertices;
        VertexNPMTexture[] vertices;

        int currentY;

        int[] LRVerts;
        int[] RRVerts;
 

        int[] TRVerts;
        int[] BRVerts;

        // Height representation

        float minimumHeight = 0;
        float maximumHeight = 200;

        float textureScale = 1.0f;

        VertexDeclaration myVertexDeclaration;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        //textures used on terrain
        Texture2D surfaceTexture;
     

        int[] indices;

        

        public float[,] heightData;

        int HSegments = 20;
        int VSegments = 20;
        int spacing = 40;

        public int terrainWidth;
        public int terrainHeight;

        Texture2D heightMap;
        Texture2D sandTexture;

        //vertex that holds normal, position, and multiple textures

        public struct VertexNPMTexture
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
    

            public static int SizeInBytes = (3 + 3 + 4) * sizeof(float);

            public VertexNPMTexture(Vector3 pos, Vector3 nor, Vector4 texc)
            {
                Position = pos;
                Normal = nor;
                TextureCoordinate = texc;              
            }


            public static VertexElement[] VertexElements = new VertexElement[]
             {
                 new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                 new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                 new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
              
             };
        }



        // I3DComponent values
        Vector3 indexPosition = Vector3.Zero;
        Vector3 position = Vector3.Zero;
        Matrix rotation = Matrix.Identity;
        Vector3 scale = new Vector3(1, 1, -1);
        BoundingBox boundingBox = new BoundingBox(new Vector3(-1), new Vector3(1));

        public Vector3 Position { get { return position; } set { position = value; } }
        public Vector3 EulerRotation
        {
            get { return MathUtil.MatrixToVector3(Rotation); }
            set { this.Rotation = MathUtil.Vector3ToMatrix(value); }
        }
        public Matrix Rotation { get { return rotation; } set { rotation = value; } }
        public Vector3 Scale { get { return scale; } set { scale = value; } }
        public BoundingBox BoundingBox { get { return boundingBox; } }

        // Constructors



        public TerrainShift(Texture2D HeightMap, Texture2D Texture, GameScreen Parent)
            : base(Parent)
        {
            Setup(HeightMap, Texture);
        }

        void Setup(Texture2D Heightmap, Texture2D Texture)
        {
            //calculate terrain width and height
            terrainHeight = spacing * VSegments;
            terrainWidth = spacing * HSegments;

            //create a heightdata object to store heightdata for every pixel of our heightmap
            createHeightData(Heightmap);


            heightMap = Heightmap;
            surfaceTexture = Texture;

            //setup effect
            effect = Engine.Content.Load<Effect>("Content/Effects/terrainEffect");
            // Load height data
            //LoadHeightData(Heightmap);


            sandTexture = Engine.Content.Load<Texture2D>("Content/Textures/sand");
       

            SetUpVertices();
            SetUpIndices();
            CalculateNormals();
            SetBuffers();
          


        }

        private void createHeightData(Texture2D heightMap)
        {
            int width = heightMap.Width;
            int height = heightMap.Height;
            
            // Get data from heightmap
            Color[] heightMapColors = new Color[width * height];
            heightMap.GetData<Color>(heightMapColors);

            // Setup height data from heightmap data

            heightData = new float[width, height];

            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    heightData[x, y] = heightMapColors[x + y * width].R;

                    if (heightData[x, y] < minimumHeight)
                        minimumHeight = heightData[x, y];
                    if (heightData[x, y] > maximumHeight)
                        maximumHeight = heightData[x, y];
                }

            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    heightData[x, y] = (heightData[x, y] - minimumHeight)
                        / (maximumHeight - minimumHeight) *60.0f;
        }


        private void SetBuffers()
        {
            vertexBuffer = new VertexBuffer(Engine.GraphicsDevice, vertices.Length * VertexNPMTexture.SizeInBytes, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            indexBuffer = new IndexBuffer(Engine.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }


        private void LoadHeightData(Texture2D heightMap)
        {
           
           

            // Setup bounding box with width and height
            boundingBox = new BoundingBox(
                new Vector3(-(HSegments * spacing) / 2, 10, -(VSegments * spacing) / 2),
                new Vector3((HSegments * spacing) / 2, 10, (VSegments * spacing) / 2));
 

          
        }



        private void SetUpVertices()
        {
            vertices = new VertexNPMTexture[HSegments * VSegments];
            LRVerts = new int[VSegments];
            RRVerts = new int[VSegments];
            TRVerts = new int[HSegments];
            BRVerts = new int[HSegments];

           

            for (int x = 0; x < HSegments; x++)
            {
                for (int y = 0; y < VSegments; y++)
                {
                    if (x == 0)
                    {
                        //add vertice to leftRow
                        LRVerts[y] = x + y * HSegments;
                        
                    }
                    if (x == HSegments -1)
                    {
                        //define right row vertices
                        RRVerts[y] = x + y * HSegments;
                    }

                    if (y == 0)
                    {
                        BRVerts[x] = x + y * HSegments;
                  
                    }

                    if (y == VSegments -1)
                    {
                        TRVerts[x] = x + y * HSegments;
                    }


                    //set height according to reference on heightData
                    int indX = (heightMap.Width/HSegments); 
                    int indY = (heightMap.Height/VSegments);
                   
                    //if x = hverts, vertice.x should get heightdata(heightmap.width)
                    //so, get wich segment of total.
                    //then multiply it with width/segmetns
                    //the length of a segment when we devide the width of heightmap into 

                    vertices[x + y * HSegments].Position = new Vector3(x * spacing, heightData[x * indX, y * indY], y * spacing);

                    vertices[x + y * HSegments].TextureCoordinate.X = (float)x / textureScale;
                    vertices[x + y * HSegments].TextureCoordinate.Y = (float)y / textureScale;
                  
                  }
            }
          

            myVertexDeclaration = new VertexDeclaration(Engine.GraphicsDevice, VertexNPMTexture.VertexElements);


        }

        private void SetUpIndices()
        {
            indices = new int[(HSegments - 1) * (VSegments - 1) * 6];
            int counter = 0;

            for (int y = 0; y < VSegments - 1; y++)
            {
                for (int x = 0; x < HSegments - 1; x++)
                {
                    int lowerLeft = x + y * HSegments;
                    int lowerRight = (x + 1) + y * HSegments;
                    int topLeft = x + (y + 1) * HSegments;
                    int topRight = (x + 1) + (y + 1) * HSegments;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }


        }

        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i].Normal = new Vector3(0, 0, 0);
            }


            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

               
                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        // Draw the terrain
        public override void Draw()
        {

            SetupEffect();
            drawTerrain(effect);

        }

        public void SetupEffect()
        {
            Matrix worldMatrix = Matrix.CreateTranslation(Position); //* Matrix.CreateTranslation(-terrainWidth / 2.0f, 0, terrainHeight / 2.0f);
            
            effect.CurrentTechnique = effect.Techniques["Textured"];
            effect.Parameters["xTexture"].SetValue(surfaceTexture);
            effect.Parameters["DefractionTex"].SetValue(heightMap);
            effect.Parameters["xView"].SetValue(Engine.Services.GetService<Camera>().View);
            effect.Parameters["xProjection"].SetValue(Engine.Services.GetService<Camera>().Projection);
            effect.Parameters["xWorld"].SetValue(worldMatrix);
            effect.Parameters["xEnableLighting"].SetValue(true);
            effect.Parameters["xLightDirection"].SetValue(new Vector3(-0.2f,-0.5f,-0.7f));
        }


        public void moveTerrain(Vector3 direction)
        {
            indexPosition += direction;
            position += direction;

            //check boundaries
            if (indexPosition.X < 0)
            {
               // indexPosition.X += spacing;

               // ReStructureIndex("LEFT");
            }
            /*
            if (indexPosition.X > spacing)
            {
                indexPosition.X -= spacing;
                    
                //reAllocateVert("RIGHT");
            }
            */

            if (indexPosition.Z < 0)
            {
                indexPosition.Z += spacing;

                ReStructureIndex("BOTTOM");

            }
            
            if (indexPosition.Z > spacing)
            {
                indexPosition.Z -= spacing;

                ReStructureIndex("TOP");

            }
              

        }


        private void ReStructureIndex(String side)
        {
            List<int> tmpindex = new List<int>();
            int counter;


            int RRtmp;
            int LRtmp;

            int TRtmp;
            int BRtmp;

            switch (side)
            {
                case "BOTTOM":

                    //get the left key of LEFT and RIGHT row (why:    an array that hold (0,4,8,12) must transform to (4,8,12,0)
                    RRtmp = RRVerts[0];
                    LRtmp = LRVerts[0];

                    // store indexes in lis
                    foreach (int var in indices)
                    {
                        tmpindex.Add(var);
                    }
                    //remove bottom row of indexed material
                    tmpindex.RemoveRange(0, ((HSegments - 1) * 6));

                    //move the bottom row vertexes to top position
                    for (int i = 0; i < BRVerts.Length; i++)
                    {
                        //move vertices to other side of terrain
                        vertices[BRVerts[i]].Position.Z += terrainHeight;
                        //set new Y position
                        int indX = (heightMap.Width / HSegments);
                        int indY = (heightMap.Height / VSegments);
                        //reset y position
                        vertices[BRVerts[i]].Position.Y = heightData[i * indX, currentY * indY];
                        //place vertices on spherical map
                        // 
                        int ypos = i - (HSegments / 2);
                        if (ypos > 0)
                        {
                            ypos *= -1;
                        }
                        vertices[BRVerts[i]].Position.Y += ypos;

                        vertices[BRVerts[i]].TextureCoordinate.Y += VSegments / textureScale;
                    }

                    //rebuild top row structure
                    //get top row indices:
                    counter = tmpindex.Count;

                    tmpindex.CopyTo(indices);

                    for (int x = 0; x < HSegments - 1; x++)
                    {
                        int lowerLeft = TRVerts[x];
                        int lowerRight = TRVerts[x + 1];
                        int topLeft = BRVerts[x];
                        int topRight = BRVerts[x + 1];

                        indices[counter++] = topLeft;
                        indices[counter++] = lowerRight;
                        indices[counter++] = lowerLeft;

                        indices[counter++] = topLeft;
                        indices[counter++] = topRight;
                        indices[counter++] = lowerRight;
                    }

                    //set the new top and bottom row
                    BRVerts.CopyTo(TRVerts, 0);
                    // fill up old bottom row with new row of vertices
                    for (int i = 0; i < HSegments; i++)
                    {

                        BRVerts[i] += HSegments;
                        if (BRVerts[i] >= vertices.Length)
                        {
                            BRVerts[i] -= vertices.Length;
                        }
                    }

                    //reallocate left and right row arrays
                    for (int i = 0; i < VSegments; i++)
                    {
                        if (i < VSegments - 1)
                        {
                            LRVerts[i] = LRVerts[i + 1];
                            RRVerts[i] = RRVerts[i + 1];
                        }
                        else
                        {
                            LRVerts[i] = LRtmp;
                            RRVerts[i] = RRtmp;
                        }
                    }



                    break;


                case "TOP":

                    currentY++;
                    if (currentY > VSegments) { currentY = 0; }
                    RRtmp = RRVerts[VSegments - 1];
                    LRtmp = LRVerts[VSegments - 1];

                    // store indexes in lis
                    foreach (int var in indices)
                    {
                        tmpindex.Add(var);
                    }
                    //remove top row of indexed material
                    tmpindex.RemoveRange(indices.Length - ((HSegments - 1) * 6), ((HSegments - 1) * 6));
                   
                    //move the top row vertexes to bottom position
                    for (int i = 0; i < TRVerts.Length; i++)
                    {
                        //move vertices to other side of terrain
                        vertices[TRVerts[i]].Position.Z -= terrainHeight;
                        //heightdata
                        
                        int indX = (heightMap.Width/HSegments); 
                        int indY = (heightMap.Height/VSegments);
                        //reset y position
                        vertices[TRVerts[i]].Position.Y = heightData[i * indX, currentY * indY];
                        //place vertices on spherical map
                       // 
                        int ypos = i - (HSegments / 2);
                        if (ypos > 0)
                        {
                            ypos *= -1;
                        }
                        vertices[TRVerts[i]].Position.Y += ypos* 10;
                        vertices[TRVerts[i]].TextureCoordinate.Y -= VSegments / textureScale;
                    }

                    //rebuild top row structure
                    //get bottom row indices:
                    counter = 0;

                    tmpindex.CopyTo(indices, ((HSegments - 1) * 6));

                    for (int x = 0; x < HSegments - 1; x++)
                    {
                        int lowerLeft = TRVerts[x];
                        int lowerRight = TRVerts[x + 1];
                        int topLeft = BRVerts[x];
                        int topRight = BRVerts[x + 1];

                        indices[counter++] = topLeft;
                        indices[counter++] = lowerRight;
                        indices[counter++] = lowerLeft;

                        indices[counter++] = topLeft;
                        indices[counter++] = topRight;
                        indices[counter++] = lowerRight;
                    }

                    // old toprow becomes new bottom row
                    TRVerts.CopyTo(BRVerts, 0);
                    // fill up old top row with new row of vertices
                    for (int i = 0; i < HSegments; i++)
                    {

                        TRVerts[i] -= HSegments;
                        if (TRVerts[i] < 0)
                        {
                            TRVerts[i] += vertices.Length;
                        }
                    }

                    //reallocate left and right row arrays
                    for (int i = VSegments - 1; i > 0; i--)
                    {
                        if (i > 0)
                        {
                            LRVerts[i] = LRVerts[i - 1];
                            RRVerts[i] = RRVerts[i - 1];
                        }
                        else
                        {
                            LRVerts[i] = LRtmp;
                            RRVerts[i] = RRtmp;
                        }
                    }



                    break;




                case "LEFT":

                    //get the left key of TOP and BOTTOM row (why:    an array that hold (0,4,8,12) must transform to (4,8,12,0)
                    TRtmp = TRVerts[0];
                    BRtmp = BRVerts[0];

                    // store indexes in lis
                    foreach (int var in indices)
                    {
                        tmpindex.Add(var);
                    }
                    //we want to remove all the indexes of a vertex row to break up the polygons.
                    //then overwrite/restructure the indexbuffer (move the empty spots to their target position)
                    //then write there the new index stucture of the layer appearing on the opposite side of the one we removed.
                    //remove left row of indexed material   (or possibly right, find out)
                    // this means, first six indexes of every vertex row must be moved to the last position of six, then make connections between our stored LRVerts and RRverts ( position change:  from [L,1,2,R] to [1,2,R,L]. 
                    // the replacing vertex indices must be allocated into the correct position of the index buffer, so they will be drawn in the correct time.

                    tmpindex.RemoveRange(0, ((HSegments - 1) * 6));

                    //move the left row vertexes to right position
                    for (int i = 0; i < BRVerts.Length; i++)
                    {
                        //move vertices to other side of terrain
                        vertices[BRVerts[i]].Position.Z += terrainHeight;
                        vertices[BRVerts[i]].TextureCoordinate.Y += VSegments;
                    }

                    //rebuild top row structure
                    //get top row indices:
                    counter = tmpindex.Count;

                    tmpindex.CopyTo(indices);

                    for (int x = 0; x < HSegments - 1; x++)
                    {
                        int lowerLeft = TRVerts[x];
                        int lowerRight = TRVerts[x + 1];
                        int topLeft = BRVerts[x];
                        int topRight = BRVerts[x + 1];

                        indices[counter++] = topLeft;
                        indices[counter++] = lowerRight;
                        indices[counter++] = lowerLeft;

                        indices[counter++] = topLeft;
                        indices[counter++] = topRight;
                        indices[counter++] = lowerRight;
                    }

                    //set the new top and bottom row
                    BRVerts.CopyTo(TRVerts, 0);
                    // fill up old bottom row with new row of vertices
                    for (int i = 0; i < HSegments; i++)
                    {

                        BRVerts[i] += HSegments;
                        if (BRVerts[i] >= vertices.Length)
                        {
                            BRVerts[i] -= vertices.Length;
                        }
                    }

                    //reallocate left and right row arrays
                    for (int i = 0; i < VSegments; i++)
                    {
                        if (i < VSegments - 1)
                        {
                            LRVerts[i] = LRVerts[i + 1];
                            RRVerts[i] = RRVerts[i + 1];
                        }
                        else
                        {
                            //LRVerts[i] = TRtmp;
                            //RRVerts[i] = RRtmp;
                        }
                    }

                    break;
            }


            //build custom function that only recalculates the new vertices and their connection (ie: so get the key values from the old right row, add the key values of the new right row, than calculate these normals
            //CalculateNormals();
         
        }


      

        public void drawTerrain(Effect effect)
        {
            // Require the camera
         
             
            Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
           // Engine.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                Engine.GraphicsDevice.VertexDeclaration = myVertexDeclaration;
                //Engine.GraphicsDevice.Indices = indexBuffer;
                //Engine.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexNPMTexture.SizeInBytes);
                Engine.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices,0, indices.Length / 3);
               // Engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
                pass.End();
            }
            effect.End();


         
            // Set the vertex winding back
            Engine.GraphicsDevice.RenderState.CullMode =
                CullMode.CullCounterClockwiseFace;


        }
    }
}

